package spring.data.jpa.repository.support;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaBuilder.In;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;

public class SpecificationImpl<T> implements Specification<T> {

    private static final long serialVersionUID = -3619616586692601273L;
    
    private Collection<SearchFilter> andFilters;
	private Collection<SearchFilter> orFilters;
	@SuppressWarnings("unused")
	private Class<T> clazz;

	public SpecificationImpl(Class<T> clazz) {
		this.clazz = clazz;
	}

	public Collection<SearchFilter> getAndFilters() {
		return andFilters;
	}

	public void setAndFilters(Collection<SearchFilter> andFilters) {
		this.andFilters = andFilters;
	}

	public Collection<SearchFilter> getOrFilters() {
		return orFilters;
	}

	public void setOrFilters(Collection<SearchFilter> orFilters) {
		this.orFilters = orFilters;
	}

	@Override
	public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
		Predicate predicate = builder.conjunction();
		if (!CollectionUtils.isEmpty(andFilters)) {
			Predicate[] predicates = buildPredicate(root, builder, this.andFilters);
			predicate = builder.and(predicates);
		}
		if (!CollectionUtils.isEmpty(orFilters)) {
			Predicate[] predicates = buildPredicate(root, builder, this.orFilters);
			Predicate or = builder.or(predicates);
			return builder.and(predicate, or);
		}
		return predicate;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private Predicate[] buildPredicate(Root<T> root, CriteriaBuilder builder, Collection<SearchFilter> filters) {
		List<Predicate> predicates = new ArrayList<>();
		if (CollectionUtils.isEmpty(filters)) {
			return predicates.toArray(new Predicate[0]);
		}
		for (SearchFilter filter : filters) {
			// nested path translate, 如Task的名为"user.name"的filedName,
			// 转换为Task.user.name属性
			String[] names = StringUtils.split(filter.getFieldName(), ".");

			Path expression = root.get(names[0]);
			for (int i = 1; i < names.length; i++) {
				expression = expression.get(names[i]);
			}
			// logic operator
			switch (filter.getOperator()) {
			case EQ:
				predicates.add(builder.equal(expression, filter.getValue()));
				break;
			case LIKE:
				predicates.add(builder.like(expression, "%" + filter.getValue() + "%"));
				break;
			case GT:
				predicates.add(builder.greaterThan(expression, (Comparable) filter.getValue()));
				break;
			case LT:
				predicates.add(builder.lessThan(expression, (Comparable) filter.getValue()));
				break;
			case GTE:
				predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.getValue()));
				break;
			case LTE:
				predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.getValue()));
				break;
			case NOT:
				predicates.add(builder.notEqual(expression, filter.getValue()));
				break;
			case IN:
				In expIn = builder.in(expression);
				inExpression(filter, expIn);
				predicates.add(expIn);
				break;
			case ISNULL:
				predicates.add(builder.isNull(expression));
				break;
			case ISNOTNULL:
				predicates.add(builder.isNotNull(expression));
				break;
			}
		}
		return predicates.toArray(new Predicate[predicates.size()]);
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
    private void inExpression(SearchFilter filter, In expIn) {
        if (filter.getValue() instanceof Collection<?>) {
        	Collection<?> values = (Collection<?>) filter.getValue();
        	for (Object obj : values) {
        		expIn.value(obj);
        	}

        } else if (filter.getValue().getClass().isArray() || filter.getValue() instanceof Object[]) {
        	Object[] values = (Object[]) filter.getValue();
        	for (Object obj : values) {
        		expIn.value(obj);
        	}
        } else {
        	expIn.value(filter.getValue());
        }
    }
}